﻿using System;
using System.Collections.Generic;
using System.Linq;
using Equinoxe.EZNFC.ACR122.Library;
using Equinoxe.EZNFC.Library;
using Equinoxe.EZNFC.Library.Tags;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Messaging;

namespace EX_NFC.FrontEnd.Windows.ViewModel
{

    /// <summary>
    /// Main view viewmodel
    /// </summary>
    /// <remarks></remarks>
    public class MainViewModel : ViewModelBase
    {

        #region Fields

        /// <summary>
        /// The device
        /// </summary>
        private INFCDevice _device;

        /// <summary>
        /// Current backlight status
        /// </summary>
        private bool _backlight;

        /// <summary>
        /// Current scrolling status
        /// </summary>
        private LCDSCrollSpeed _lcdScrollSpeed;

        /// <summary>
        /// Current scrolling direction
        /// </summary>
        private LCDScrollDirection _lcdScrollDirection;

        #endregion


        #region Properties

        /// <summary>
        /// Gets the window title.
        /// </summary>
        /// <remarks></remarks>
        public string WindowTitle
        {
            get
            {
                return string.Format("EZ-NFC {0}", System.Reflection.Assembly.GetExecutingAssembly().GetName().Version);
            }
        }

        /// <summary>
        /// Busy indicator status backing field
        /// </summary>
        private bool _isBusy;
        /// <summary>
        /// Gets or sets a value indicating whether this instance is busy.
        /// </summary>
        /// <value><c>true</c> if this instance is busy; otherwise, <c>false</c>.</value>
        /// <remarks></remarks>
        public bool IsBusy
        {
            get { return _isBusy; }
            set { _isBusy = value; RaisePropertyChanged("IsBusy"); }
        }

        /// <summary>
        /// 
        /// </summary>
        private string _busyMessage;
        /// <summary>
        /// Gets or sets the busy message.
        /// </summary>
        /// <value>The busy message.</value>
        /// <remarks></remarks>
        public string BusyMessage
        {
            get { return _busyMessage; }
            set { _busyMessage = value; RaisePropertyChanged("BusyMessage"); }
        }

        /// <summary>
        /// 
        /// </summary>
        private bool _isDeviceConnected;
        /// <summary>
        /// Gets or sets a value indicating whether the device is connected.
        /// </summary>
        /// <value><c>true</c> if device is connected; otherwise, <c>false</c>.</value>
        /// <remarks></remarks>
        public bool IsDeviceConnected
        {
            get { return _isDeviceConnected; }
            set
            {
                _isDeviceConnected = value;
                RaisePropertyChanged("IsDeviceConnected");
                RaisePropertyChanged("LCDRows");
                RaisePropertyChanged("ContrastLevels");
                RaisePropertyChanged("LEDItems");
                RaisePropertyChanged("LCDEnabled");
                RaisePropertyChanged("BuzzerEnabled");
                RaisePropertyChanged("LEDEnabled");
            }
        }

        /// <summary>
        /// Gets the COM ports backing field.
        /// </summary>
        private IList<string> _ports;

        /// <summary>
        /// Gets the COM ports.
        /// </summary>
        /// <remarks></remarks>
        public IList<string> Ports
        {
            get
            {
                if (_ports == null)
                {
                    _ports = new List<string>();
                    foreach (var port in Enum.GetNames(typeof(Ports)))
                    {
                        _ports.Add(port);
                    }
                }
                return _ports;
            }
        }

        /// <summary>
        /// Selected port backing field.
        /// </summary>
        private string _selectedPort;
        /// <summary>
        /// Gets or sets the selected port.
        /// </summary>
        /// <value>The selected port.</value>
        /// <remarks></remarks>
        public string SelectedPort
        {
            get { return _selectedPort; }
            set
            {
                _selectedPort = value;
                RaisePropertyChanged("SelectedPort");
            }
        }

        /// <summary>
        /// 
        /// </summary>
        private NFCDeviceInfo _deviceInfo;
        /// <summary>
        /// Gets or sets the device info.
        /// </summary>
        /// <value>The device info.</value>
        /// <remarks></remarks>
        public NFCDeviceInfo DeviceInfo
        {
            get { return _deviceInfo; }
            set { _deviceInfo = value; RaisePropertyChanged("DeviceInfo"); }
        }

        /// <summary>
        /// 
        /// </summary>
        private string _lcdMessage;
        /// <summary>
        /// Gets or sets the LCD message.
        /// </summary>
        /// <value>The LCD message.</value>
        /// <remarks></remarks>
        public string LCDMessage
        {
            get { return _lcdMessage; }
            set
            {
                _lcdMessage = value;
                RaisePropertyChanged("LCDMessage");
            }
        }

        /// <summary>
        /// Gets the LCD rows.
        /// </summary>
        /// <remarks></remarks>
        public IEnumerable<string> LCDRows
        {
            get
            {
                if (_deviceInfo == null)
                    yield break;
                for (var i = 0; i < _deviceInfo.NumberOfLCDRows; i++)
                {
                    yield return string.Format(Properties.Resources.LCD_Line, i);
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        private int _selectedLCDRow;
        /// <summary>
        /// Gets or sets the selected LCD row.
        /// </summary>
        /// <value>The selected LCD row.</value>
        /// <remarks></remarks>
        public int SelectedLCDRow
        {
            get { return _selectedLCDRow; }
            set { _selectedLCDRow = value; RaisePropertyChanged("SelectedLCDRow"); }
        }



        /// <summary>
        /// Gets or sets a value indicating whether LCD should scroll left to right.
        /// </summary>
        /// <value><c>true</c> if LCD should scroll left to right; otherwise, <c>false</c>.</value>
        /// <remarks></remarks>
        public bool LCDScrollL2R
        {
            get { return _lcdScrollDirection == LCDScrollDirection.LeftToRight; }
            set
            {
                if (value)
                    _lcdScrollDirection = LCDScrollDirection.LeftToRight;
                RaisePropertyChanged("LCDScrollL2R");
                RaisePropertyChanged("LCDScrollR2L");
                RaisePropertyChanged("LCDScrollT2B");
                RaisePropertyChanged("LCDScrollB2T");
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether LCD should scroll right to left.
        /// </summary>
        /// <value><c>true</c> if LCD should scroll right to left; otherwise, <c>false</c>.</value>
        /// <remarks></remarks>
        public bool LCDScrollR2L
        {
            get { return _lcdScrollDirection == LCDScrollDirection.RightToLeft; }
            set
            {
                if (value)
                    _lcdScrollDirection = LCDScrollDirection.RightToLeft;
                RaisePropertyChanged("LCDScrollL2R");
                RaisePropertyChanged("LCDScrollR2L");
                RaisePropertyChanged("LCDScrollT2B");
                RaisePropertyChanged("LCDScrollB2T");
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether LCD should scroll top to bottom.
        /// </summary>
        /// <value><c>true</c> if LCD should scroll top to bottom; otherwise, <c>false</c>.</value>
        /// <remarks></remarks>
        public bool LCDScrollT2B
        {
            get { return _lcdScrollDirection == LCDScrollDirection.TopToBottom; }
            set
            {
                if (value)
                    _lcdScrollDirection = LCDScrollDirection.TopToBottom;
                RaisePropertyChanged("LCDScrollL2R");
                RaisePropertyChanged("LCDScrollR2L");
                RaisePropertyChanged("LCDScrollT2B");
                RaisePropertyChanged("LCDScrollB2T");
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether LCD should scroll bottom to top.
        /// </summary>
        /// <value><c>true</c> if LCD should scroll bottom to top; otherwise, <c>false</c>.</value>
        /// <remarks></remarks>
        public bool LCDScrollB2T
        {
            get { return _lcdScrollDirection == LCDScrollDirection.BottomToTop; }
            set
            {
                if (value)
                    _lcdScrollDirection = LCDScrollDirection.BottomToTop;
                RaisePropertyChanged("LCDScrollL2R");
                RaisePropertyChanged("LCDScrollR2L");
                RaisePropertyChanged("LCDScrollT2B");
                RaisePropertyChanged("LCDScrollB2T");
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether LCD scroll speed should be slow.
        /// </summary>
        /// <value><c>true</c> if LCD scroll speed should be slow; otherwise, <c>false</c>.</value>
        /// <remarks></remarks>
        public bool LCDScrollSpeedSlow
        {
            get { return _lcdScrollSpeed == LCDSCrollSpeed.Slow; }
            set
            {
                if (value)
                    _lcdScrollSpeed = LCDSCrollSpeed.Slow;
                RaisePropertyChanged("LCDScrollSpeedSlow");
                RaisePropertyChanged("LCDScrollSpeedMedium");
                RaisePropertyChanged("LCDScrollSpeedFast");
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether LCD scroll speed should be medium.
        /// </summary>
        /// <value><c>true</c> if LCD scroll speed should be medium; otherwise, <c>false</c>.</value>
        /// <remarks></remarks>
        public bool LCDScrollSpeedMedium
        {
            get { return _lcdScrollSpeed == LCDSCrollSpeed.Medium; }
            set
            {
                if (value)
                    _lcdScrollSpeed = LCDSCrollSpeed.Medium;
                RaisePropertyChanged("LCDScrollSpeedSlow");
                RaisePropertyChanged("LCDScrollSpeedMedium");
                RaisePropertyChanged("LCDScrollSpeedFast");
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether LCD scroll speed should be fast.
        /// </summary>
        /// <value><c>true</c> if LCD scroll speed should be fast; otherwise, <c>false</c>.</value>
        /// <remarks></remarks>
        public bool LCDScrollSpeedFast
        {
            get { return _lcdScrollSpeed == LCDSCrollSpeed.Fast; }
            set
            {
                if (value)
                    _lcdScrollSpeed = LCDSCrollSpeed.Fast;
                RaisePropertyChanged("LCDScrollSpeedSlow");
                RaisePropertyChanged("LCDScrollSpeedMedium");
                RaisePropertyChanged("LCDScrollSpeedFast");
            }
        }

        /// <summary>
        /// 
        /// </summary>
        private int _buzzerOnDuration = 100;
        /// <summary>
        /// Gets or sets the duration of the buzzer on.
        /// </summary>
        /// <value>The duration of the buzzer on.</value>
        /// <remarks></remarks>
        public int BuzzerOnDuration
        {
            get { return _buzzerOnDuration; }
            set
            {
                _buzzerOnDuration = value;
                RaisePropertyChanged("BuzzerOnDuration");
            }
        }

        /// <summary>
        /// Duration of the buzzer off backing field.
        /// </summary>
        private int _buzzerOffDuration = 100;
        /// <summary>
        /// Gets or sets the duration of the buzzer off.
        /// </summary>
        /// <value>The duration of the buzzer off.</value>
        /// <remarks></remarks>
        public int BuzzerOffDuration
        {
            get { return _buzzerOffDuration; }
            set { _buzzerOffDuration = value; RaisePropertyChanged("BuzzerOffDuration"); }
        }

        /// <summary>
        /// Buzzer repeat backing field.
        /// </summary>
        private int _buzzerRepeat = 1;
        /// <summary>
        /// Gets or sets the buzzer repeat.
        /// </summary>
        /// <value>The buzzer repeat.</value>
        /// <remarks></remarks>
        public int BuzzerRepeat
        {
            get { return _buzzerRepeat; }
            set { _buzzerRepeat = value; RaisePropertyChanged("BuzzerRepeat"); }
        }

        /// <summary>
        /// LED items backing field.
        /// </summary>
        private IList<LEDItem> _LEDItems = null;

        /// <summary>
        /// Gets the LED items.
        /// </summary>
        /// <remarks></remarks>
        public IList<LEDItem> LEDItems
        {
            get
            {
                if ((_LEDItems == null) && (_device != null))
                {
                    _LEDItems = new List<LEDItem>();

                    for (var i = 0; i < _deviceInfo.NumberOfLEDs; i++)
                    {
                        _LEDItems.Add(new LEDItem { Name = string.Format("LED {0}", i + 1), State = false });
                    }

                }
                return _LEDItems;
            }
        }

        /// <summary>
        /// Gets the contrast levels.
        /// </summary>
        /// <remarks></remarks>
        public IEnumerable<string> ContrastLevels
        {
            get
            {
                if (_deviceInfo == null)
                    yield break;
                for (var i = 0; i < _deviceInfo.LCDContrastLevels; i++)
                {
                    yield return string.Format(Properties.Resources.LCD_ContrastLevel, i);
                }

            }
        }

        /// <summary>
        /// Gets or sets the index of the selected contrast.
        /// </summary>
        /// <value>The index of the selected contrast.</value>
        /// <remarks></remarks>
        public int SelectedContrastIndex { get; set; }


        /// <summary>
        /// Gets a value indicating whether LCD is enabled.
        /// </summary>
        /// <remarks></remarks>
        public bool LCDEnabled
        {
            get
            {
                return ((_device != null) && (_device.DeviceInfo.HasLCD));
            }
        }

        /// <summary>
        /// Gets a value indicating whether buzzer is enabled.
        /// </summary>
        /// <remarks></remarks>
        public bool BuzzerEnabled
        {
            get
            {
                return ((_device != null) && (_device.DeviceInfo.HasBuzzer));
            }
        }

        /// <summary>
        /// Gets a value indicating whether LED is enabled.
        /// </summary>
        /// <remarks></remarks>
        public bool LEDEnabled
        {
            get
            {
                return ((_device != null) && (_device.DeviceInfo.NumberOfLEDs > 0));
            }
        }

        #endregion


        #region Commands

        /// <summary>
        /// Command connect backing field.
        /// </summary>
        private RelayCommand _commandConnect;

        /// <summary>
        /// Gets the command connect.
        /// </summary>
        /// <remarks></remarks>
        public RelayCommand CommandConnect
        {
            get
            {
                return _commandConnect ?? (_commandConnect = new RelayCommand(() =>
                {
                    try
                    {
                        IsBusy = true;
                        BusyMessage = Properties.Resources.BusyMessage_Connecting;

                        _device = ACR122Device.CreateDevice(DeviceCharset.Ansi, (Ports)Enum.Parse(typeof(Ports), SelectedPort, true), ACR122Device.DeviceInfoStatic.BaudsAvailable.FirstOrDefault());
                        if (_device == null)
                        {
                            return;
                        }
                        _device.OpenAsync(DeviceOpened, (e) =>
                        {
                            BusyMessage = Properties.Resources.DeviceInfo_ConnectingToDevice;
                            IsBusy = false;
                            Messenger.Default.Send(new GenericMessage<Exception>(e), MessageType.ErrorMessage);
                        });
                    }
                    catch (Exception e)
                    {
                        Messenger.Default.Send(new GenericMessage<Exception>(e), MessageType.ErrorMessage);
                    }
                }));
            }
        }


        /// <summary>
        /// Command disconnect backing field.
        /// </summary>
        private RelayCommand _commandDisconnect;

        /// <summary>
        /// Gets the command disconnect.
        /// </summary>
        /// <remarks></remarks>
        public RelayCommand CommandDisconnect
        {
            get
            {
                return _commandDisconnect ?? (_commandDisconnect = new RelayCommand(() =>
                {
                    _device.LCDBacklight(false);
                    _device.LCDClear();
                    _device.Close();
                    _device = null;
                    IsDeviceConnected = false;

                }));
            }
        }



        /// <summary>
        /// Command LCD backlight backing field.
        /// </summary>
        private RelayCommand _commandLCDBacklight;

        /// <summary>
        /// Gets the command LCD backlight.
        /// </summary>
        /// <remarks></remarks>
        public RelayCommand CommandLCDBacklight
        {
            get
            {
                return _commandLCDBacklight ?? (_commandLCDBacklight = new RelayCommand(() =>
                {
                    _backlight = !_backlight;
                    _device.LCDBacklight(_backlight);
                }));
            }
        }

        /// <summary>
        /// Command LCD contrast backing field.
        /// </summary>
        private RelayCommand _commandLCDContrast;

        /// <summary>
        /// Gets the command LCD contrast.
        /// </summary>
        /// <remarks></remarks>
        public RelayCommand CommandLCDContrast
        {
            get
            {
                return _commandLCDContrast ?? (_commandLCDContrast = new RelayCommand(() =>
                {
                    _device.LCDContrast(SelectedContrastIndex);
                }));
            }
        }

        /// <summary>
        /// Command LCD clear backing field.
        /// </summary>
        private RelayCommand _commandLCDClear;

        /// <summary>
        /// Gets the command LCD clear.
        /// </summary>
        /// <remarks></remarks>
        public RelayCommand CommandLCDClear
        {
            get
            {
                return _commandLCDClear ?? (_commandLCDClear = new RelayCommand(() => _device.LCDClear()));
            }
        }

        /// <summary>
        /// Command LCD scroll backing field.
        /// </summary>
        private RelayCommand _commandLCDScroll;

        /// <summary>
        /// Gets the command LCD scroll.
        /// </summary>
        /// <remarks></remarks>
        public RelayCommand CommandLCDScroll
        {
            get
            {
                return _commandLCDScroll ?? (_commandLCDScroll = new RelayCommand(() => _device.LCDScroll(_lcdScrollSpeed, _lcdScrollDirection)));
            }
        }

        /// <summary>
        /// Command LCD pause scroll backing field.
        /// </summary>
        private RelayCommand _commandLCDPauseScroll;

        /// <summary>
        /// Gets the command LCD pause scroll.
        /// </summary>
        /// <remarks></remarks>
        public RelayCommand CommandLCDPauseScroll
        {
            get
            {
                return _commandLCDPauseScroll ?? (_commandLCDPauseScroll = new RelayCommand(() => _device.LCDPauseScroll()));
            }
        }

        /// <summary>
        /// Command LCD stop scroll backing field.
        /// </summary>
        private RelayCommand _commandLCDStopScroll;

        /// <summary>
        /// Gets the command LCD stop scroll.
        /// </summary>
        /// <remarks></remarks>
        public RelayCommand CommandLCDStopScroll
        {
            get
            {
                return _commandLCDStopScroll ?? (_commandLCDStopScroll = new RelayCommand(() => _device.LCDStopScroll()));
            }
        }

        /// <summary>
        /// Command LCD message backing field.
        /// </summary>
        private RelayCommand _commandLCDMessage;

        /// <summary>
        /// Gets the command LCD message.
        /// </summary>
        /// <remarks></remarks>
        public RelayCommand CommandLCDMessage
        {
            get
            {
                return _commandLCDMessage ?? (_commandLCDMessage = new RelayCommand(() => _device.DisplayLCDMessage(LCDMessage, SelectedLCDRow)));
            }
        }

        /// <summary>
        /// Command buzzer backing field.
        /// </summary>
        private RelayCommand _commandBuzzer;

        /// <summary>
        /// Gets the command buzzer.
        /// </summary>
        /// <remarks></remarks>
        public RelayCommand CommandBuzzer
        {
            get
            {
                return _commandBuzzer ?? (_commandBuzzer = new RelayCommand(() => _device.Buzzer(BuzzerOnDuration, BuzzerOffDuration, BuzzerRepeat)));
            }
        }

        /// <summary>
        /// Command set LEDs backing field.
        /// </summary>
        private RelayCommand _commandSetLEDs;

        /// <summary>
        /// Gets the command set LEDs.
        /// </summary>
        /// <remarks></remarks>
        public RelayCommand CommandSetLEDs
        {
            get
            {
                return _commandSetLEDs ?? (_commandSetLEDs = new RelayCommand(() => _device.SetLEDS(LEDItems.Select(d => d.State))));
            }
        }

        #endregion


        /// <summary>
        /// Initializes a new instance of the MainViewModel class.
        /// </summary>
        /// <remarks></remarks>
        public MainViewModel()
        {
            if (IsInDesignMode)
            {
                // Code runs in Blend --> create design time data.
            }
            else
            {
                // Code runs "for real"
                Messenger.Default.Register<GenericMessage<string>>(this, MessageType.ImageSelected, (msg) => _device.DisplayLCDImage(msg.Content));
            }
        }

        /// <summary>
        /// Device opened callback.
        /// </summary>
        /// <remarks></remarks>
        private void DeviceOpened()
        {
            try
            {
                LCDScrollSpeedMedium = true;
                LCDScrollR2L = true;

                _device.LCDClear();
                _device.LCDStopScroll();
                _device.LCDBacklight(false);

                DeviceInfo = _device.DeviceInfo;

                IsBusy = false;

                IsDeviceConnected = true;

                _device.TagDetected += (sender, args) => ViewModelLocator.Mifare1KStatic.Tag = args.NFCTag as Mifare1K;

                _device.StartPolling();

                Messenger.Default.Send(new GenericMessage<StatusData>(this, new StatusData { StatusType = StatusType.DeviceStatus, Message = "ACR 122L" }), MessageType.StatusMessage);

                Messenger.Default.Send(new GenericMessage<StatusData>(this, new StatusData { StatusType = StatusType.NoErrorStatus }), MessageType.StatusMessage);
            }
            catch (Exception e)
            {
                Messenger.Default.Send(new GenericMessage<Exception>(e), MessageType.ErrorMessage);
                Messenger.Default.Send(new GenericMessage<StatusData>(this, new StatusData { StatusType = StatusType.DeviceStatus, Message = Properties.Resources.None }), MessageType.StatusMessage);
                Messenger.Default.Send(new GenericMessage<StatusData>(this, new StatusData { StatusType = StatusType.ErrorStatus, Message = Properties.Resources.Error_DeviceOpening }), MessageType.StatusMessage);
            }
        }

        /// <summary>
        /// Unregisters this instance from the Messenger class.
        /// <para>To cleanup additional resources, override this method, clean
        /// up and then call base.Cleanup().</para>
        /// </summary>
        /// <remarks></remarks>
        public override void Cleanup()
        {
            // Clean up if needed

            base.Cleanup();

            if (IsDeviceConnected)
            {
                _device.StopPolling();
                _device.LCDBacklight(false);
                _device.LCDClear();
                _device.Close();
                IsDeviceConnected = false;
            }

        }
    }

    /// <summary>
    /// Single LED class
    /// </summary>
    /// <remarks></remarks>
    public class LEDItem
    {
        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        /// <value>The name.</value>
        /// <remarks></remarks>
        public string Name { get; set; }

        /// <summary>
        /// Gets or sets a value indicating whether this <see cref="LEDItem"/> is on.
        /// </summary>
        /// <value><c>true</c> if on; otherwise, <c>false</c>.</value>
        /// <remarks></remarks>
        public bool State { get; set; }


    }
}